/*
 * LDIR
 * 
 * LDIR, a GPS cartography application built for Let's Do It Romania eco 
 * campaign.
 * Copyright (C) 2010 Genera Software <office@generasoftware.com>
 * 
 * http://generasoftware.com/
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 */
#include <fbs.h>
#include <imageconversion.h>
#include <bitmaptransforms.h>
#include <bautils.h>
#include <gulicon.h>
#include <eikenv.h>
#ifndef __UIQ3__
#include <pathinfo.h>
#endif // __UIQ3__
#ifdef __SERIES60_3X__
#include <akniconutils.h>
#endif // __SERIES60_3X__

#include "ImageUtils.h"
#include "bitmapmethods.h"
#include "Logger.h"
#include "StringUtils.h"

#define min(a,b)    (((a) < (b)) ? (a) : (b))
#define max(a,b)    (((a) > (b)) ? (a) : (b))


CFbsBitmap* ImageUtils::LoadImageL(const TDesC& aFilename, const TSize* aDesiredSize)
{
    RFs fsSession;
    User::LeaveIfError(fsSession.Connect());
    CFbsBitmap* bitmap = NULL;
    TRequestStatus requestStatus;

    if (!BaflUtils::FileExists(fsSession, aFilename))
    {
        fsSession.Close();
        return NULL;
    }

    TFileName filename = aFilename;
    filename.UpperCase();
    TParse parser;
    parser.Set(filename, NULL, NULL);

    for (TInt i = 0; i < KImagesExtensionsCount; ++i)
    {
        if (parser.Ext().Compare(ImagesExtensions[i]) == 0)
        {
            CImageDecoder* imageDecoder;
            TRAPD(errorCode, imageDecoder = CImageDecoder::FileNewL(fsSession, filename, CImageDecoder::EOptionAlwaysThread));
            if (errorCode == KErrNone)
            {
                CleanupStack::PushL(imageDecoder);
                const TFrameInfo& frameInfo = imageDecoder->FrameInfo();

                TSize imageSize = frameInfo.iOverallSizeInPixels;
                if (aDesiredSize)
                {
                    TUint hScaleFactor = imageSize.iWidth / aDesiredSize->iWidth;
                    TUint vScaleFactor = imageSize.iHeight / aDesiredSize->iHeight;
                    TUint scaleFactor = min(hScaleFactor, vScaleFactor);
                    if (scaleFactor >= 4)
                        scaleFactor = 4;
                    else if (scaleFactor >= 2)
                        scaleFactor = 2;
                    else
                        scaleFactor = 1;

                    TUint hCorrection = 0;
                    TUint vCorrection = 0;
                    if (imageSize.iWidth % scaleFactor)
                        hCorrection = 1;
                    if (imageSize.iHeight % scaleFactor)
                        vCorrection = 1;
                    imageSize = TSize(imageSize.iWidth / scaleFactor + hCorrection, imageSize.iHeight / scaleFactor + vCorrection);
                }

                bitmap = new (ELeave) CFbsBitmap();
                CleanupStack::PushL(bitmap);
                User::LeaveIfError(bitmap->Create(imageSize, EColor16M));

                imageDecoder->Convert(&requestStatus, *bitmap);
                User::WaitForRequest(requestStatus);

                CleanupStack::Pop(bitmap);
                CleanupStack::PopAndDestroy(imageDecoder);
            }
            break;
        }
    }
    fsSession.Close();
    return bitmap;
}


TInt ImageUtils::SaveImageL(CFbsBitmap& aBitmap, const TDesC& aFilename, const TDesC8* aMimeType)
{
    TInt result = KErrNone;
    RFs fsSession;
    User::LeaveIfError(fsSession.Connect());
    BaflUtils::EnsurePathExistsL(fsSession, aFilename);

    TBuf8<32> mimeType = _L8("image/jpeg");;
    if (NULL != aMimeType)
        mimeType = *aMimeType;

    TRequestStatus requestStatus;
    if (mimeType == _L8("image/jpeg"))
    {
        CImageEncoder* imageEncoder = CImageEncoder::FileNewL(fsSession, aFilename, CImageEncoder::EOptionAlwaysThread, KImageTypeJPGUid);
        CleanupStack::PushL(imageEncoder);

        TJpegImageData* imageData = new (ELeave) TJpegImageData;
        imageData->iSampleScheme = TJpegImageData::EColor444;
        imageData->iQualityFactor = 90;

        CFrameImageData* frameImageData = CFrameImageData::NewL();
        CleanupStack::PushL(frameImageData);
        User::LeaveIfError(frameImageData->AppendImageData(imageData));

        imageEncoder->Convert(&requestStatus, aBitmap, frameImageData);
        User::WaitForRequest(requestStatus);

        CleanupStack::PopAndDestroy(frameImageData);
        CleanupStack::PopAndDestroy(imageEncoder);
    }
    else
    {
        CImageEncoder* imageEncoder = CImageEncoder::FileNewL(fsSession, aFilename, mimeType, CImageEncoder::EOptionAlwaysThread);
        CleanupStack::PushL(imageEncoder);
        imageEncoder->Convert(&requestStatus, aBitmap);
        User::WaitForRequest(requestStatus);
        CleanupStack::PopAndDestroy(imageEncoder);
    }
    fsSession.Close();
    return result;
}

TInt ImageUtils::CopyBitmapL(CFbsBitmap* aSource, CFbsBitmap*& aDestination)
{
    aDestination = NBitmapMethods::CreateBitmapLC(aSource->SizeInPixels(), aSource->DisplayMode());
    CFbsBitmapDevice* targetDevice = NBitmapMethods::CreateBitmapDeviceLC(*aDestination);
    CFbsBitGc* targetGc = NBitmapMethods::CreateGraphicsContextLC(*targetDevice);
    targetGc->BitBlt(TPoint(0, 0), aSource);
    CleanupStack::PopAndDestroy(targetGc);
    CleanupStack::PopAndDestroy(targetDevice);
    CleanupStack::Pop(aDestination);

    return KErrNone;
}

CGulIcon* ImageUtils::CreateIconL(const TDesC& aBitmapFile, TInt aBitmapId, TInt aMaskId, const TSize& aSize)
{
#ifdef __SERIES60_3X__
    CFbsBitmap* bitmap     = NULL;
    CFbsBitmap* mask = NULL;
    TInt bitmapId(aBitmapId);
    TInt maskId(aMaskId);
    if (AknIconUtils::IsMifFile(aBitmapFile))
    {
        AknIconUtils::ValidateLogicalAppIconId (aBitmapFile, bitmapId, maskId);
    }
    AknIconUtils::CreateIconLC(bitmap, mask, aBitmapFile, bitmapId, maskId);
    AknIconUtils::SetSize(bitmap, aSize, EAspectRatioPreserved);
    AknIconUtils::SetSize(mask, aSize, EAspectRatioPreserved);
    CGulIcon* icon = CGulIcon::NewL(bitmap,mask);
    CleanupStack::Pop(2);
    return icon;
#else
    return CEikonEnv::Static()->CreateIconL(aBitmapFile, aBitmapId, aMaskId);
#endif
}

CDesCArray* ImageUtils::GetAvailableImagesLC(const TDesC& aDirPath)
{
    CDesCArrayFlat* filesList = new (ELeave) CDesCArrayFlat(1);
    CleanupStack::PushL(filesList);
    CDesCArrayFlat* dirList = new (ELeave) CDesCArrayFlat(1);
    CleanupStack::PushL(dirList);

    if (aDirPath.Length() == 0)
    {
#ifdef __UIQ3__
#else
        HBufC* path = HBufC::NewLC(KMaxFileName);
        TPtr pathPtr = path->Des();
        pathPtr.Copy(PathInfo::PhoneMemoryRootPath());
        pathPtr.Append(PathInfo::ImagesPath());
        dirList->AppendL(*path);
        pathPtr.Copy(PathInfo::MemoryCardRootPath());
        pathPtr.Append(PathInfo::MmsBackgroundImagesPath());
        dirList->AppendL(*path);
        pathPtr.Copy(PathInfo::MemoryCardRootPath());
        pathPtr.Append(PathInfo::ImagesPath());
        dirList->AppendL(*path);
        pathPtr.Copy(PathInfo::MemoryCardRootPath());
        pathPtr.Append(PathInfo::MmsBackgroundImagesPath());
        dirList->AppendL(*path);
        CleanupStack::PopAndDestroy(path);
#endif // __UIQ3__
    }
    else
    {
        dirList->AppendL(aDirPath);
    }

    RFs fsSession;
    User::LeaveIfError(fsSession.Connect());
    while (dirList->Count() > 0)
    {
        BrowseForFilesL(dirList, filesList, fsSession);
    }
    fsSession.Close();

    CleanupStack::PopAndDestroy(dirList);
    return filesList;
}


void ImageUtils::BrowseForFilesL(CDesCArray* aDirList, CDesCArray* aFilesList, RFs& aFsSession)
{
    CDir* dirList;
    CDir* filesList;
    TFileName fileName;
    TFileName parentDirName = (*aDirList)[0];
    TInt i = 0;
    aDirList->Delete(0);

    if (KErrNone != aFsSession.GetDir(parentDirName, KEntryAttNormal, ESortByName | EAscending, filesList, dirList))
        return;
    CleanupStack::PushL(filesList);
    CleanupStack::PushL(dirList);

    for (i = 0; (NULL != filesList) && (i < filesList->Count()); i++)
    {
        fileName = parentDirName;
        fileName += (*filesList)[i].iName;
        if (MatchesExtension(fileName))
            aFilesList->AppendL(fileName);
    }
    for (i = 0; (NULL != dirList) && (i < dirList->Count()); i++)
    {
        fileName = parentDirName;
        fileName += (*dirList)[i].iName;
        fileName += _L("\\");
        aDirList->AppendL(fileName);
    }

    CleanupStack::PopAndDestroy(dirList);
    CleanupStack::PopAndDestroy(filesList);
}

TBool ImageUtils::MatchesExtension(TFileName& aFilename)
{
    aFilename.UpperCase();
    TParse fileParse;
    fileParse.Set(aFilename, NULL, NULL);
    for (TInt i = 0; i < KImagesExtensionsCount; ++i)
    {
        if (fileParse.Ext() == ImagesExtensions[i])
            return ETrue;
    }
    return EFalse;
}


// =============================================================================
// CImageHandler, a utility class for loading images.
// =============================================================================

// ============================= MEMBER FUNCTIONS ===============================

// -----------------------------------------------------------------------------
// CImageHandler::CImageHandler
// C++ constructor can NOT contain any code, that
// might leave.
// -----------------------------------------------------------------------------
//
CImageHandler::CImageHandler(RFs& aFs,
                             MImageHandlerCallback* aCallback,
                             CFbsBitmap* aBitmap,
                             CFbsBitmap* aBitmapMask)
                             : CActive(CActive::EPriorityStandard),
                             iDecoder(NULL),
                             iCallback(aCallback),
                             iBitmap(aBitmap),
                             iBitmapMask(aBitmapMask),
                             iFs(aFs)
{
    CActiveScheduler::Add(this);
}

// -----------------------------------------------------------------------------
// CImageHandler::ConstructL
// Symbian 2nd phase constructor can leave.
// -----------------------------------------------------------------------------
//
void CImageHandler::ConstructL()
{
}

// -----------------------------------------------------------------------------
// CImageHandler::NewLC
// Two-phased constructor.
// -----------------------------------------------------------------------------
//
CImageHandler* CImageHandler::NewL(RFs& aFs, MImageHandlerCallback* aCallback, CFbsBitmap* aBitmap, CFbsBitmap* aBitmapMask/* = NULL*/)
{
    CImageHandler* self = NewLC(aFs, aCallback, aBitmap, aBitmapMask);
    CleanupStack::Pop();
    return self;
}

// -----------------------------------------------------------------------------
// CImageHandler::NewLC
// Two-phased constructor.
// -----------------------------------------------------------------------------
//
CImageHandler* CImageHandler::NewLC(RFs& aFs, MImageHandlerCallback* aCallback, CFbsBitmap* aBitmap, CFbsBitmap* aBitmapMask/* = NULL*/)
{
    CImageHandler* self = new (ELeave) CImageHandler(aFs, aCallback, aBitmap, aBitmapMask);
    CleanupStack::PushL( self );
    self->ConstructL();

    return self;
}

// Destructor
CImageHandler::~CImageHandler()
{
    Cancel();
    delete iDecoder;
    delete iRotator;
    delete iScaler;
}

// -----------------------------------------------------------------------------
// CImageHandler::SetBitmapL
// Set the destination bitmap.
// -----------------------------------------------------------------------------
//
TBool CImageHandler::SetBitmapL( CFbsBitmap *aBitmap, CFbsBitmap* aBitmapMask )
{
    if ( IsActive() )
    {
        // If active, fail.
        return EFalse;
    }
    // else
    iBitmap = aBitmap;
    iBitmapMask = aBitmapMask;
    return ETrue;
}

// -----------------------------------------------------------------------------
// CImageHandler::LoadAsyncFileL
// Loads a selected frame from a named file
// -----------------------------------------------------------------------------
//
void CImageHandler::LoadFileL( const TFileName& aFileName,  TSize* aDesiredSize /*= NULL*/, TInt aSelectedFrame /*= 0*/ )
{
    LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - START"));

    LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - IsActive = %d"), IsActive());
    __ASSERT_ALWAYS(!IsActive(), User::Invariant());
    if ( iDecoder )
    {
        delete iDecoder;
        iDecoder = NULL;
    }
    iCurrentOperation = EImgOpLoad;
    TFileName test;
    test = aFileName;
    iDecoder = CImageDecoder::FileNewL(iFs, aFileName, CImageDecoder::EOptionAlwaysThread);
    // Get image information
    iFrameInfo = iDecoder->FrameInfo(aSelectedFrame);
    LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - selected frame = %d"), aSelectedFrame);

    // Resize to fit.
    TRect bitmapSize = iFrameInfo.iFrameCoordsInPixels;
    if (aDesiredSize)
        iBitmap->Resize(LoadingImageSize(bitmapSize.Size(), *aDesiredSize));
    else
        iBitmap->Resize(bitmapSize.Size());

    LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - selected frame size = %d, %d"), bitmapSize.Size().iWidth, bitmapSize.Size().iHeight);

    // Check for alpha channel availability
    if ((iFrameInfo.iFlags & (TFrameInfo::ETransparencyPossible | TFrameInfo::EAlphaChannel)) && iBitmapMask)
    {
        LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - picture has alpha"));
        // Decode as bitmap + alpha channel mask.
        iBitmapMask->Resize(bitmapSize.Size());
        iDecoder->Convert(&iStatus, *iBitmap, *iBitmapMask, aSelectedFrame);
    }
    else
    {
        LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - picture has no alpha"));
        // Decode as bitmap.
        iDecoder->Convert(&iStatus, *iBitmap, aSelectedFrame);
    }

    SetActive();
    LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - END"));
}


// -----------------------------------------------------------------------------
// CImageHandler::LoadAsyncFileL
// Loads a selected frame from a named file
// -----------------------------------------------------------------------------
//
void CImageHandler::LoadDesL( const TDesC8& aData,  TSize* aDesiredSize /*= NULL*/, TInt aSelectedFrame /*= 0*/ )
{
    LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - START"));
    LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - IsActive = %d"), IsActive());;
    __ASSERT_ALWAYS(!IsActive(), User::Invariant());
    if ( iDecoder )
    {
        delete iDecoder;
        iDecoder = NULL;
    }
    iCurrentOperation = EImgOpLoad;
    iDecoder = CImageDecoder::DataNewL(iFs, aData, CImageDecoder::EOptionAlwaysThread);
    // Get image information
    iFrameInfo = iDecoder->FrameInfo(aSelectedFrame);
    LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - selected frame = %d"), aSelectedFrame);

    // Resize to fit.
    TRect bitmapSize = iFrameInfo.iFrameCoordsInPixels;
    if (aDesiredSize)
        iBitmap->Resize(LoadingImageSize(bitmapSize.Size(), *aDesiredSize));
    else
        iBitmap->Resize(bitmapSize.Size());

    LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - selected frame size = %d, %d"), bitmapSize.Size().iWidth, bitmapSize.Size().iHeight);

    // Check for alpha channel availability
    if ((iFrameInfo.iFlags & (TFrameInfo::ETransparencyPossible | TFrameInfo::EAlphaChannel)) && iBitmapMask)
    {
        LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - picture has alpha"));
        // Decode as bitmap + alpha channel mask.
        iBitmapMask->Resize(bitmapSize.Size());
        iDecoder->Convert(&iStatus, *iBitmap, *iBitmapMask, aSelectedFrame);
    }
    else
    {
        LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - picture has no alpha"));
        // Decode as bitmap.
        iDecoder->Convert(&iStatus, *iBitmap, aSelectedFrame);
    }

    SetActive();
    LOG(EVerbosityImageUtils, _L("CImageHandler::LoadFileL() - END"));
}


// -----------------------------------------------------------------------------
// CImageHandler::FrameInfo
// Get the current frame information.
// -----------------------------------------------------------------------------
//
const TFrameInfo& CImageHandler::FrameInfo() const
{
    return iFrameInfo;
}

// -----------------------------------------------------------------------------
// CImageHandler::RunL
// CActive::RunL() implementation. Called on image load success/failure.
// -----------------------------------------------------------------------------
//
void CImageHandler::RunL()
{
    LOG(EVerbosityImageUtils, _L("CImageHandler::RunL() - START"));
    if (iStatus.Int() == KErrNone)
    {
        if (iCurrentOperation == EImgOpScale && iPendingMaskOperation)
        {
            iScaler->Scale(&iStatus, *iBitmapMask, iScaleSize, iKeepAspectRatio);
            SetActive();
            iPendingMaskOperation = EFalse;
            return;
        }
    }

    delete iDecoder;
    iDecoder = NULL;
    delete iScaler;
    iScaler = NULL;
    delete iRotator;
    iRotator = NULL;

    // Invoke callback.
    if (iCallback)
        iCallback->ImageOperationCompleteL(iCurrentOperation, iStatus.Int());
    LOG(EVerbosityImageUtils, _L("CImageHandler::RunL() - END"));
}

// -----------------------------------------------------------------------------
// CImageHandler::DoCancel
// CActive::Cancel() implementation. Stops decoding.
// -----------------------------------------------------------------------------
//
void CImageHandler::DoCancel()
{
    if (iDecoder)
        iDecoder->Cancel();
    if (iRotator)
        iRotator->Cancel();
    if (iScaler)
        iScaler->Cancel();
}


TSize CImageHandler::LoadingImageSize(const TSize& aActualSize, const TSize& aDesiredSize)
{
    TSize imageSize = aActualSize;

    TInt hScaleFactor = imageSize.iWidth / aDesiredSize.iWidth;
    TInt vScaleFactor = imageSize.iHeight / aDesiredSize.iHeight;
    TInt scaleFactor = Min(hScaleFactor, vScaleFactor);
    if (scaleFactor >= 8)
        scaleFactor = 8;
    else if (scaleFactor >= 4)
        scaleFactor = 4;
    else if (scaleFactor >= 2)
        scaleFactor = 2;
    else
        scaleFactor = 1;

    TUint hCorrection = 0;
    TUint vCorrection = 0;
    if (imageSize.iWidth % scaleFactor)
        hCorrection = 1;
    if (imageSize.iHeight % scaleFactor)
        vCorrection = 1;
    imageSize = TSize(imageSize.iWidth / scaleFactor + hCorrection, imageSize.iHeight / scaleFactor + vCorrection);

    return imageSize;
}

void CImageHandler::ScaleL(const TSize& aSize, TBool aKeepAspectRatio /* = ETrue */)
{
    LOG(EVerbosityImageUtils, _L("CImageHandler::ScaleL() - START."));
    LOG(EVerbosityImageUtils, _L("CImageHandler::ScaleL() - IsActive = %d"), IsActive());

    __ASSERT_ALWAYS(!IsActive(), User::Invariant());

    if (iScaler)
    {
        delete iScaler;
        iScaler = NULL;
    }

    iCurrentOperation = EImgOpScale;
    iScaleSize = aSize;
    iKeepAspectRatio = aKeepAspectRatio;
    iScaler = CBitmapScaler::NewL();
    iScaler->Scale(&iStatus, *iBitmap, aSize, aKeepAspectRatio);
    if (iBitmapMask)
        iPendingMaskOperation = ETrue;
    SetActive();
    LOG(EVerbosityImageUtils, _L("CImageHandler::ScaleL() - END."));
}


